/**
 * 总结
 * 1. 使用Proxy代理实现数据响应
 * 2. 在proxy对象的getter方法里进行依赖收集
 * 3. 在proxy对象的setter方法里进行值的变化通知
 * 4. 收集依赖时，使用两层的嵌套Map进行先收集响应式对象，再收集每个对象的key
 * 5. 优点，使用proxy代理不必担心新的属性的响应式丢失问题，因为新属性仍然会走getter逻辑，依赖会被收集。同理，数组也不需要再hack重写那6个方法了。
 */

let activeEffect;
class Dep {
  subs = new Set();
  depend() {
    if (activeEffect) {
      this.subs.add(activeEffect);
    }
  }
  notify() {
    this.subs.forEach((effect) => {
      effect();
    });
  }
}

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null; // 保证了activeEffect只被收集一次
}

// WeakMap: 1.只能使用object作为key 2.一旦作为key的object不再被任何代码所访问，则该object可以被解除在map中的关联，从而实现被垃圾回收 3.不是iterator对象
const targetMap = new WeakMap();

function getDep(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    // 添加Map容器
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let dep = depsMap.get(key);
  if (!dep) {
    // 为新添加的属性收集依赖
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

const reactiveHandlers = {
  get(target, key, receiver) {
    const dep = getDep(target, key);
    dep.depend();
    // 使用Reflect对对象进行get属性值的操作会更加规范，特别是针对带有原型链访问的场景
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    const dep = getDep(target, key);
    // 同样使用Reflect对对象进行set属性值操作，但会有result作为返回值，true表示成功，false表示失败，该值必须返回，以使得Proxy对象正常工作。
    const result = Reflect.set(target, key, value, receiver);
    dep.notify();
    return result;
  },
  // has(){

  // },
  // ownKeys(){

  // }
};

// vue3风格: 响应式依赖在对象上
function reactive(raw) {
  return new Proxy(raw, reactiveHandlers);
}

const state = reactive({
  count: 123,
});

watchEffect(() => {
  console.log(state.count);
  state.msg = '1'
  console.log(state.msg);
});

state.count++; // 被监听函数会再次执行
state.msg = "hello"; // vue3给对象添加新属性时，不用担心响应式失去的问题，会自动收集依赖，不像vue2需要手动$set

// vue2风格: 响应式依赖在对象的属性的setter和getter上(注意：这对数组不生效)
// function reactive(raw) {
//   Object.keys(raw).forEach((key) => {
//     const dep = new Dep();
//     let value = raw[key];

//     Object.defineProperty(raw, key, {
//       get() {
//         dep.depend();
//         return value;
//       },
//       set(newVal) {
//         value = newVal;
//         dep.notify();
//       },
//     });
//   });

//   return raw;
// }
